package com.hongji.lms5xxcommunicator.opc.util;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.stack.core.AttributeId;
import org.eclipse.milo.opcua.stack.core.Identifiers;
import org.eclipse.milo.opcua.stack.core.types.builtin.*;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned;
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
import org.eclipse.milo.opcua.stack.core.types.structured.ReadValueId;
import org.eclipse.milo.opcua.stack.core.types.structured.WriteValue;

import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

@Slf4j
@RequiredArgsConstructor
public class OpcIoHelper {

    private final OpcUaClient client;
    private final OpcNodeResolver resolver;

    // 缓存每个节点的数据类型，减少反复读取 DataType 属性的 RPC
    private final Map<String, NodeId> dataTypeCache = new ConcurrentHashMap<>();

    /** 统一解析：支持 nsu=URI;i=… / ns=4;i=… / ns=4;id=…（id 自动转 i） */
    public NodeId resolveFlexible(String rawNode) {
        if (rawNode == null || rawNode.trim().isEmpty()) {
            throw new IllegalArgumentException("rawNode must not be blank");
        }
        return resolver.resolveFlexible(client, rawNode);
    }

    /** 读 double（灵活） */
    public double readDoubleFlexible(String rawNode) throws Exception {
        if (rawNode == null || rawNode.trim().isEmpty()) return Double.NaN;
        NodeId id = resolveFlexible(rawNode);
        DataValue dv = client.readValue(0.0, TimestampsToReturn.Both, id).get(5, TimeUnit.SECONDS);
        Object v = dv.getValue() != null ? dv.getValue().getValue() : null;
        if (v instanceof Number) return ((Number) v).doubleValue();
        try { return Double.parseDouble(String.valueOf(v).trim()); }
        catch (Exception e) { return Double.NaN; }
    }

    /** 获取节点 DataType（直接读取 DataType 属性，跨版本通用） */
    private NodeId getNodeDataType(NodeId nodeId) throws Exception {
        String key = nodeId.toParseableString();
        NodeId cached = dataTypeCache.get(key);
        if (cached != null) return cached;

        // 读取 AttributeId.DataType
        ReadValueId r = new ReadValueId(nodeId, AttributeId.DataType.uid(), null, QualifiedName.NULL_VALUE);
        DataValue dv = client.read(0.0, TimestampsToReturn.Neither, Collections.singletonList(r))
                .get(5, TimeUnit.SECONDS).getResults()[0];

        Variant var = dv.getValue();
        Object val = (var != null) ? var.getValue() : null;
        if (!(val instanceof NodeId)) {
            throw new IllegalStateException("Unexpected DataType attribute value for " + key + ": " + val);
        }
        NodeId dt = (NodeId) val;
        dataTypeCache.put(key, dt);
        return dt;
    }

    /** 根据服务器声明的 DataType，把 value 转成正确的 Java 类型，避免 Bad_TypeMismatch */
    private Object coerceForDataType(NodeId dataTypeId, Object value) {
        if (value == null) return null;
        if (value instanceof Variant || value instanceof ExtensionObject) return value;

        try {
            if (dataTypeId.equals(Identifiers.Float)) {                 // REAL(32)
                if (value instanceof Number) return ((Number) value).floatValue();
                if (value instanceof String) return Float.parseFloat(((String) value).trim());
            } else if (dataTypeId.equals(Identifiers.Double)) {         // LREAL(64)
                if (value instanceof Number) return ((Number) value).doubleValue();
                if (value instanceof String) return Double.parseDouble(((String) value).trim());
            } else if (dataTypeId.equals(Identifiers.Int16)) {
                if (value instanceof Number) return ((Number) value).shortValue();
                if (value instanceof String) return Short.parseShort(((String) value).trim());
            } else if (dataTypeId.equals(Identifiers.Int32)) {          // DINT
                if (value instanceof Number) return ((Number) value).intValue();
                if (value instanceof String) return Integer.parseInt(((String) value).trim());
            } else if (dataTypeId.equals(Identifiers.Int64)) {
                if (value instanceof Number) return ((Number) value).longValue();
                if (value instanceof String) return Long.parseLong(((String) value).trim());
            } else if (dataTypeId.equals(Identifiers.UInt16)) {
                if (value instanceof Number) return Unsigned.ushort(((Number) value).intValue());
                if (value instanceof String) return Unsigned.ushort(Integer.parseInt(((String) value).trim()));
            } else if (dataTypeId.equals(Identifiers.UInt32)) {
                if (value instanceof Number) return Unsigned.uint(((Number) value).longValue());
                if (value instanceof String) return Unsigned.uint(Long.parseLong(((String) value).trim()));
            } else if (dataTypeId.equals(Identifiers.Boolean)) {
                if (value instanceof Boolean) return value;
                if (value instanceof Number) return ((Number) value).intValue() != 0;
                if (value instanceof String) return Boolean.parseBoolean(((String) value).trim());
            } else if (dataTypeId.equals(Identifiers.String)) {
                return String.valueOf(value);
            }
            // 其它类型先原样交给服务器
            return value;
        } catch (Exception ex) {
            log.debug("coerceForDataType failed for {}: {} -> {}", dataTypeId, value, ex.toString());
            return value;
        }
    }

    /** 类型感知写入：自动按 DataType 转型；并对 NaN 做兜底（部分 PLC 不接受 NaN） */
    public void writeIfPresent(String rawNode, Object value) {
        if (rawNode == null || rawNode.trim().isEmpty()) return;
        try {
            NodeId id = resolveFlexible(rawNode);
            NodeId dt = getNodeDataType(id);
            Object coerced = coerceForDataType(dt, value);

            // 有些 PLC/服务器不接受 NaN：兜底成 0（如需保留 NaN，可删除这两行）
            if (coerced instanceof Float && ((Float) coerced).isNaN())  coerced = 0.0f;
            if (coerced instanceof Double && ((Double) coerced).isNaN()) coerced = 0.0d;

            DataValue dv = new DataValue(new Variant(coerced), StatusCode.GOOD, null, DateTime.NULL_VALUE);
            WriteValue wv = new WriteValue(id, AttributeId.Value.uid(), null, dv);
            StatusCode sc = client.write(Collections.singletonList(wv)).get().getResults()[0];

            log.info("写入结果sc.isGood = {}", sc.isGood());
            if (!sc.isGood()) {
                log.warn("写入失败 [{}] -> {} (as {} for DataType={}) ：{}",
                        rawNode, value, (coerced==null? "null" : coerced.getClass().getSimpleName()), dt, sc);
            }
        } catch (Exception e) {
            log.warn("写入异常 [{}] -> {} ：{}", rawNode, value, e.toString());
        }
    }

    /** 直接读 Value（需要 NodeId 时可用） */
    public DataValue readValue(NodeId nodeId) throws Exception {
        ReadValueId r = new ReadValueId(nodeId, AttributeId.Value.uid(), null, QualifiedName.NULL_VALUE);
        return client.read(0.0, TimestampsToReturn.Both, java.util.Collections.singletonList(r))
                .get(5, TimeUnit.SECONDS).getResults()[0];
    }
}
